package com.wei.czz.framework.admin.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.constant.Constant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.dto.admin.menu.MenuDto;
import com.wei.czz.common.enums.ResultEnum;
import com.wei.czz.common.enums.admin.menu.MenuTypeEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.po.UserPo;
import com.wei.czz.common.tool.PageDto;
import com.wei.czz.common.tool.PageQuery;
import com.wei.czz.common.utils.CacheKeyUtils;
import com.wei.czz.common.utils.CopyUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.common.utils.SecurityUtils;
import com.wei.czz.framework.admin.service.MenuService;
import com.wei.czz.framework.common.service.SnowflakeService;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.common.vo.admin.menu.MenuFormVo;
import com.wei.czz.common.vo.admin.menu.MenuVo;
import com.wei.czz.framework.admin.dao.MenuDao;
import com.wei.czz.framework.admin.entity.MenuEntity;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-03-15 10:30:18
 * className: MenuServiceImpl 系统菜单操作请求服务接口实现类
 * version: 1.0
 * description:
 */
@Service("menuService")
@AllArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuDao, MenuEntity> implements MenuService {

    private static final Logger log = LoggerFactory.getLogger(MenuServiceImpl.class);

    private final RedisStringHandler redisStringHandler;

    private final RedisHashHandler redisHashHandler;

    private final RedisHandler redisHandler;

    private final RedissonClient redissonClient;

    private final SnowflakeService snowflakeService;

    private final ThrottleHandler throttleHandler;

    @Override
    public void saveOrEdit(MenuEntity menu) {
        Long menuId = menu.getMenuId();
        if (Objects.isNull(menuId)) {
            menuId = snowflakeService.get();

            menu.setMenuId(menuId);
            int count = baseMapper.insert(menu);
            log.info("插入菜单完成。count={}", count);
        } else {
            int count = baseMapper.updateById(menu);
            log.info("修改菜单完成。count={}", count);
        }
    }

    @Transactional
    @Override
    public void batchSaveMenu(List<MenuEntity> menuList) {
        // 操作数据库系统菜单表，批量保存菜单数据
        boolean bool = this.saveBatch(menuList);
        log.info("批量保存菜单完成。bool={}", bool);
    }

    @Override
    public void cacheMenu(List<Long> menuIdList) {
        // 查询数据库系统菜单表，批量获取菜单信息
        List<MenuEntity> menuList = this.listByIds(menuIdList);
        if (menuList.isEmpty()) {
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "菜单数据不存在，操作失败。");
        }
        // 数据映射
        Map<String, MenuEntity> menuMap = new HashMap<>();
        for (MenuEntity menu : menuList) {
            menuMap.put(menu.getMenuId().toString(), menu);
        }
        // 操作Redis缓存，批量新增菜单缓存信息
        redisHashHandler.putAll(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuMap);
    }

    @Override
    public void cacheAllMenu() {
        // 查询数据库系统菜单表，获取所有菜单信息
        List<MenuEntity> menuList = this.list();
        if (menuList.isEmpty()) {
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "系统菜单为空，请确认，如有问题，请联系管理员处理");
        }
        // 数据映射
        Map<String, MenuEntity> menuMap = new HashMap<>();
        for (MenuEntity menu : menuList) {
            menuMap.put(menu.getMenuId().toString(), menu);
        }
        // 操作Redis缓存，批量新增菜单缓存信息
        redisHashHandler.putAll(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuMap);
    }

    @Override
    public MenuEntity getMenu(Long menuId) {

        String _menuId = menuId.toString();
        // 从缓存获取菜单数据
        MenuEntity menu = redisHashHandler.get(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), _menuId);
        if (Objects.nonNull(menu)) {
            log.info("从缓存中获取到菜单对象。{}", menu);
            return menu;
        }

        String lockKey = RedisConstant.LOCK + CacheKeyUtils.getRedisHashKey(EntityConstant.MENU) + _menuId;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        try {
            // double check
            menu = redisHashHandler.get(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), _menuId);
            if (Objects.nonNull(menu)) {
                log.info("double check操作，从缓存中获取到菜单对象。{}", menu);
                return menu;
            }

            menu = baseMapper.selectById(menuId);
            if (Objects.isNull(menu)) {
                log.info("菜单不存在。menuId={}", menuId);
                // 菜单数据为空
                throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "菜单不存在，请确认。");
            }
            if (CommonEnum.ONE.getValue().equals(menu.getDeleteStatus())) {
                log.info("菜单已删除。menuId={}", menuId);
                throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "菜单不存在，请确认。");
            }

            // 将菜单存储到缓存中
            redisHashHandler.put(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), _menuId, menu);

            return menu;
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    @Override
    public PageDto<MenuEntity> getPageMenuList(MenuFormVo menuVo) {

        // 构造mp分页对象
        Page<MenuEntity> page = new Page<>(menuVo.getPage(), menuVo.getLimit());

        // 判断是否包含检索内容
        boolean notWord = StringUtils.isBlank(menuVo.getWord());

        LambdaQueryWrapper<MenuEntity> menuLambdaWrapper = new LambdaQueryWrapper<>();
        // 匹配 菜单名称、显示状态和菜单状态 字段
        menuLambdaWrapper.eq(notWord, MenuEntity::getParentId,
                        Optional.ofNullable(menuVo.getParentId()).orElse(CommonEnum.ZERO.getLongValue()))
                .like(!notWord, MenuEntity::getName, menuVo.getWord())
                .eq(Objects.nonNull(menuVo.getType()), MenuEntity::getType, menuVo.getType())
                .eq(Objects.nonNull(menuVo.getDisplay()), MenuEntity::getDisplay, menuVo.getDisplay())
                .eq(Objects.nonNull(menuVo.getStatus()), MenuEntity::getStatus, menuVo.getStatus())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 根据 排序等级和创建时间 进行排序
        menuLambdaWrapper.orderByAsc(Arrays.asList(MenuEntity::getSort, MenuEntity::getCreateTime));
        // 查询数据库系统菜单表，分页查询一级菜单数据
        baseMapper.selectPage(page, menuLambdaWrapper);

        List<MenuEntity> menuList = page.getRecords();
        if (!menuList.isEmpty() && notWord) {
            // 查询子菜单数据列表
            List<MenuEntity> childrenMenuList = this.batchQueryChildrenMenuList(menuList, menuVo);
            menuList.addAll(childrenMenuList);
        }

        return new PageDto<>(page);
    }

    @Override
    public Map<String, Object> getCacheMenuList(MenuFormVo menuFormVo) {
        // 查询Redis缓存，获取所有菜单缓存键
        Set<String> menuIdSet = redisHashHandler.keys(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU));

        // 查询数据库系统菜单表，获取所有菜单id
        List<String> menuIdList = baseMapper.getMenuIdList();

        List<String> residueMenuIds = new ArrayList<>(menuIdList.size());
        // 数据库存储的数据和缓存数据进行取交集操作
        for (String menuId : menuIdList) {
            if (menuFormVo.getCacheStatus()) {
                if (menuIdSet.contains(menuId)) {
                    residueMenuIds.add(menuId);
                }
            } else {
                if (!menuIdSet.contains(menuId)) {
                    residueMenuIds.add(menuId);
                }
            }
        }
        List<MenuDto> menuDtoList = new ArrayList<>();
        long size = 0;
        if (!residueMenuIds.isEmpty()) {
            // 构造mp分页对象
            Page<MenuDto> page = PageQuery.initPage(menuFormVo);
            // 查询数据库系统菜单表，分页查询菜单列表
            baseMapper.getCacheMenuList(page, residueMenuIds, menuFormVo);

            menuDtoList = page.getRecords();
            size = page.getTotal();
        }

        Map<String, Object> result = new HashMap<>();
        result.put("cacheMenuSize", size);
        result.put("page", new PageDto<>(menuDtoList, size, menuFormVo.getPage(), menuFormVo.getLimit()));
        return result;
    }

    @Override
    public List<MenuEntity> getList(List<Long> idList) {
        if (CollectionUtils.isEmpty(idList)) {
            log.info("查询菜单操作，传入菜单主键列表为空");
            return Collections.emptyList();
        }
        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        menuLambdaQueryWrapper.in(MenuEntity::getMenuId, idList)
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 查询
        List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
        log.info("查询菜单完成。size={}", menuList.size());
        return menuList;
    }

    @Override
    public List<MenuEntity> findList(List<Long> parentIdList) {
        if (CollectionUtils.isEmpty(parentIdList)) {
            log.info("获取直接子菜单，传入父级菜单主键列表为空");
            return Collections.emptyList();
        }
        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        menuLambdaQueryWrapper.in(MenuEntity::getParentId, parentIdList)
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 查询
        List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
        log.info("查询直接子菜单完成。size={}", menuList.size());
        return menuList;
    }

    @Override
    public List<MenuEntity> findList(Long userId) {
        List<MenuEntity> menuList = baseMapper.selectUserMenuList(userId, CommonEnum.ZERO.getValue(),
                CommonEnum.ZERO.getValue());
        log.info("查询用户关联菜单完成。userId={} size={}", userId, menuList.size());
        return menuList;
    }

    @Override
    public MenuDto getMenuTree(Long menuId) {
        // 菜单树缓存键
        String menuTreeKey = RedisConstant.STRING + EntityConstant.MENU + Constant.UNDER + RedisConstant.TREE
                + menuId;

        // 查询Redis缓存，查询菜单树的根节点
        MenuDto resultMenuDto = redisStringHandler.get(menuTreeKey);
        if (resultMenuDto != null) {
            log.info("从redis缓存中得到菜单树");
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(menuTreeKey, 150, RandomNumUtils.random5Minutes(10));
            }, menuTreeKey, 1500, TimeUnit.MILLISECONDS);
            return resultMenuDto;
        }
        // 获取Redisson分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + menuTreeKey);
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis缓存，查询菜单树的根节点
            resultMenuDto = redisStringHandler.get(menuTreeKey);
            if (resultMenuDto != null) {
                log.info("double check 从redis缓存中得到菜单树");
                return resultMenuDto;
            }
            // 查询`目录`、`页面`菜单
            List<Integer> typeList = new ArrayList<>(2);
            typeList.add(MenuTypeEnum.PACKAGE_TYPE.getValue());
            typeList.add(MenuTypeEnum.PAGE_TYPE.getValue());

            LambdaQueryWrapper<MenuEntity> menuLambdaWrapper = new LambdaQueryWrapper<>();
            // 匹配 菜单类型和删除状态 字段
            menuLambdaWrapper.in(MenuEntity::getType, typeList)
                    .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
            // 查询数据库系统菜单表，获取所有菜单（所有菜单默认已经按id升序排序）
            List<MenuEntity> menuList = this.list(menuLambdaWrapper);

            // 构造菜单树
            TreeSet<MenuDto> menuTree = getMenuTree(menuList, menuId);

            // 最顶层菜单项
            resultMenuDto = new MenuDto();
            resultMenuDto.setMenuId(CommonEnum.ZERO.getStringValue());
            resultMenuDto.setMenuPath(StringUtils.EMPTY);
            resultMenuDto.setName("顶级菜单");
            resultMenuDto.setType(CommonEnum.ZERO.getValue());
            resultMenuDto.setStatus(CommonEnum.ZERO.getValue());
            resultMenuDto.setDisabled(Boolean.FALSE);
            resultMenuDto.setChildren(menuTree);

            // 缓存菜单树五分钟
            redisStringHandler.set(menuTreeKey, resultMenuDto, RandomNumUtils.random5Minutes(10),
                    TimeUnit.SECONDS);

            return resultMenuDto;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    @Override
    public List<MenuEntity> getMenuList(Integer status) {
        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        menuLambdaQueryWrapper.eq(Objects.nonNull(status), MenuEntity::getStatus, CommonEnum.ZERO.getValue())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 查询数据库系统菜单表，获取菜单数据
        List<MenuEntity> menuList = baseMapper.selectList(menuLambdaQueryWrapper);
        log.info("查询菜单完成。status={} size={}", status, menuList.size());
        return menuList;
    }

    @Override
    public List<MenuEntity> getChildrenMenuList(String menuPath) {
        List<MenuEntity> menuList = baseMapper.getChildrenMenuList(menuPath, CommonEnum.ZERO.getValue());
        log.info("根据菜单路径查询子菜单完成。menuPath={} size={}", menuPath, menuList.size());
        return menuList;
    }

    @Override
    public Map<String, Object> getMenuOnCache(MenuVo menuVo) {
        String menuId = menuVo.getMenuId().toString();
        // 查询Redis缓存，获取菜单信息
        MenuEntity menu = redisHashHandler.get(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuId);
        if (menu == null) {
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(),
                    "您查询的菜单缓存信息不存在，请确认，如有问题，请联系管理员处理。");
        }
        Map<String, Object> result = new HashMap<>(8);
        result.put("structKey", CacheKeyUtils.getRedisHashKey(EntityConstant.MENU));
        result.put("valueKey", menuId);
        result.put("data", menu);
        return result;
    }

    @Override
    public Integer getNextSort(Long menuId) {
        Integer maxSort = baseMapper.getNextSort(menuId, CommonEnum.ZERO.getValue());
        if (maxSort == null) {
            maxSort = 0;
        }
        return maxSort + 1;
    }

    @Transactional
    @Override
    public void batchUpdateMenu(List<MenuEntity> menuList) {
        boolean bool = this.updateBatchById(menuList);
        log.info("批量修改菜单完成。bool={}", bool);
    }

    @Override
    public void deleteMenu(Long menuId) {
        // 获取正在删除菜单数据的用户id
        UserPo userPo = SecurityUtils.getUser();
        Date date = new Date();

        LambdaUpdateWrapper<MenuEntity> menuLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        // 设置 删除状态、更新时间和操作更新的用户 字段值
        menuLambdaUpdateWrapper.set(MenuEntity::getDeleteStatus, CommonEnum.ONE.getValue())
                .set(MenuEntity::getUpdateTime, date)
                .set(MenuEntity::getUpdateUser, userPo.getUsername())
                .set(MenuEntity::getUpdateUserId, userPo.getUserId());
        // 匹配 菜单主键 字段
        menuLambdaUpdateWrapper.eq(MenuEntity::getMenuId, menuId)
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 操作数据库系统菜单信息表，删除系统菜单数据
        int count = baseMapper.update(menuLambdaUpdateWrapper);
        log.info("删除菜单操作完成。count={}", count);
        if (count == 0) {
            throw new CzzException(ResultEnum.REQUEST_FAIL.getCode(), "菜单数据不存在，操作失败。");
        }
    }

    @Override
    public void deleteMenuOnCache(String[] menuIds) {
        // 操作Redis缓存，批量删除菜单缓存信息
        redisHashHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU), menuIds);
    }

    @Override
    public void clearMenuOnCache() {
        // 操作Redis缓存，清空菜单缓存信息
        redisHandler.delete(CacheKeyUtils.getRedisHashKey(EntityConstant.MENU));
    }

    /**
     * 生成菜单查询对象
     */
    public LambdaQueryWrapper<MenuEntity> buildMenuQueryWrapper() {
        LambdaQueryWrapper<MenuEntity> menuLambdaWrapper = new LambdaQueryWrapper<>();
        // 查询 菜单id、上级菜单id、父级菜单路径、菜单名称、菜单路由、菜单权限、菜单类型、菜单图标、排序等级、显隐状态、菜单状态、创建时间和操作创建的用户 字段
        menuLambdaWrapper.select(MenuEntity::getMenuId, MenuEntity::getParentId, MenuEntity::getMenuPath,
                MenuEntity::getName, MenuEntity::getPath, MenuEntity::getPermit, MenuEntity::getType,
                MenuEntity::getIcon, MenuEntity::getSort, MenuEntity::getDisplay, MenuEntity::getStatus,
                MenuEntity::getCreateTime, MenuEntity::getCreateUserId);
        return menuLambdaWrapper;
    }

    /**
     * 批量查询子菜单数据
     * @param menuList 菜单数据列表
     * @return 所有子菜单数据
     */
    private List<MenuEntity> batchQueryChildrenMenuList(List<MenuEntity> menuList, MenuFormVo menuVo) {
        LambdaQueryWrapper<MenuEntity> menuLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 匹配 菜单名称、显示状态和菜单状态 字段
        menuLambdaQueryWrapper.and(_menuLambdaQueryWrapper -> {
                    for (MenuEntity menu : menuList) {
                        String menuPath = menu.getMenuPath() + menu.getMenuId() + Constant.SPLIT;
                        _menuLambdaQueryWrapper.likeRight(MenuEntity::getMenuPath, menuPath)
                                .or();
                    }
                })
                .eq(Objects.nonNull(menuVo.getType()), MenuEntity::getType, menuVo.getType())
                .eq(Objects.nonNull(menuVo.getDisplay()), MenuEntity::getDisplay, menuVo.getDisplay())
                .eq(Objects.nonNull(menuVo.getStatus()), MenuEntity::getStatus, menuVo.getStatus())
                .eq(MenuEntity::getDeleteStatus, CommonEnum.ZERO.getValue());
        // 查询子菜单
        List<MenuEntity> childrenMenuList = baseMapper.selectList(menuLambdaQueryWrapper);
        log.info("查询子菜单完成。size={}", childrenMenuList.size());
        return childrenMenuList;
    }

    /**
     * 构造菜单数结构
     * @param menuList 菜单数据列表
     * @param menuId   需要禁用的菜单主键
     * @return 一级菜单集合
     */
    private TreeSet<MenuDto> getMenuTree(List<MenuEntity> menuList, Long menuId) {
        // 菜单映射map
        Map<String, MenuDto> menuMap = new HashMap<>();
        // 创建存储第一级菜单的集合
        TreeSet<MenuDto> menuTree = new TreeSet<>();
        // 需要延后处理的菜单
        List<MenuDto> lastOptMenuList = new ArrayList<>();

        String disableMenuId = Optional.ofNullable(menuId).map(Objects::toString).orElse(StringUtils.SPACE);

        for (MenuEntity menu : menuList) {

            MenuDto menuDto = CopyUtils.map(menu, MenuDto.class);
            menuDto.setDisabled(Boolean.FALSE);
            if (menu.getMenuId().equals(menuId) || CommonEnum.ONE.getValue().equals(menu.getStatus())
                    || menu.getMenuPath().contains(disableMenuId)) {
                /*
                    以下两种情况，将该菜单状态设置为禁用
                    1、菜单主键和参数menuId一致
                    2、父级菜单路径包含参数menuId
                 */
                menuDto.setDisabled(Boolean.TRUE);
            }

            // 映射构造
            menuMap.put(menuDto.getMenuId(), menuDto);

            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (Objects.isNull(parentMenuDto)) {
                if (CommonEnum.ZERO.getStringValue().equals(menuDto.getParentId())) {
                    // 如果菜单的父级菜单id为0，则默认该菜单为一级菜单
                    menuTree.add(menuDto);
                } else {
                    // 延后处理
                    lastOptMenuList.add(menuDto);
                }
                continue;
            }
            TreeSet<MenuDto> children = parentMenuDto.getChildren();
            if (children == null) {
                children = new TreeSet<>();
                parentMenuDto.setChildren(children);
            }
            children.add(menuDto);
        }
        for (MenuDto menuDto : lastOptMenuList) {
            MenuDto parentMenuDto = menuMap.get(menuDto.getParentId());
            if (parentMenuDto == null) {
                continue;
            }
            TreeSet<MenuDto> children = parentMenuDto.getChildren();
            if (children == null) {
                children = new TreeSet<>();
                parentMenuDto.setChildren(children);
            }
            children.add(menuDto);
        }
        return menuTree;
    }

}